package com.zondy.mapgis.psmap.manager.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.zondy.mapgis.common.core.exception.ServiceException;
import com.zondy.mapgis.common.core.utils.DateUtils;
import com.zondy.mapgis.common.core.utils.JsonUtils;
import com.zondy.mapgis.common.core.utils.StringUtils;
import com.zondy.mapgis.common.core.utils.bean.BeanUtils;
import com.zondy.mapgis.common.core.utils.bean.BeanValidators;
import com.zondy.mapgis.common.json.JacksonUtils;
import com.zondy.mapgis.common.security.utils.SecurityUtils;
import com.zondy.mapgis.domain.psmap.AppData;
import com.zondy.mapgis.domain.psmap.AppDataParamConfig;
import com.zondy.mapgis.domain.psmap.AppDataTreeNode;
import com.zondy.mapgis.domain.system.SysRole;
import com.zondy.mapgis.domain.system.SysUser;
import com.zondy.mapgis.psmap.manager.constant.CloudProtocolType;
import com.zondy.mapgis.psmap.manager.constant.DataConstants;
import com.zondy.mapgis.psmap.manager.constant.DataServiceType;
import com.zondy.mapgis.psmap.manager.mapper.AppDataMapper;
import com.zondy.mapgis.psmap.manager.mapper.AppRoleDataMapper;
import com.zondy.mapgis.psmap.manager.service.IAppDataService;
import com.zondy.mapgis.system.api.service.SysServiceProxy;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Validator;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class AppDataServiceImpl extends ServiceImpl<AppDataMapper, AppData> implements IAppDataService {
    @Resource
    private AppDataMapper appDataMapper;
    @Resource
    private AppRoleDataMapper appRoleDataMapper;
    @Resource
    private SysServiceProxy sysServiceProxy;
    @Resource
    protected Validator validator;
    private final AtomicLong idCounter = new AtomicLong();

    @Override
    public AppData selectAppDataByDataId(Long dataId) {
        return this.appDataMapper.selectAppDataByDataId(dataId);
    }

    @Override
    public List<AppData> selectAppDataTreeByUserId(Long userId) {
        boolean isAdmin = SysUser.isAdmin(userId) || SecurityUtils.getLoginUser().getUser().getRoles().stream().map(SysRole::getRoleId).anyMatch(SysRole::isAdmin);
        List<AppData> appDataList;
        if (isAdmin) {
            AppData parentAppData = this.appDataMapper.selectAppDataByDataId(DataConstants.ROOT_DATA_ID);
            List<AppData> childrenAppData = this.appDataMapper.selectAppDataChildrenByDataId(DataConstants.ROOT_DATA_ID);
            appDataList = CollUtil.union(childrenAppData, Collections.singletonList(parentAppData)).stream().toList();
        } else {
            appDataList = this.appDataMapper.selectAppDataTreeByUserId(userId);
        }

        return appDataList;
    }

    @Override
    public List<AppData> selectAppDataTreeForManagerByUserId(Long userId) {
        List<AppData> appDataList = this.selectAppDataTreeByUserId(userId);
        return this.buildTreeDataForManager(appDataList, DataConstants.ROOT_DATA_ID);
    }

    @Override
    public List<AppDataTreeNode> selectAppDataTreeForMapByUserId(Long userId) {
        List<AppData> appDataList = this.selectAppDataTreeByUserId(userId);
        return this.buildTreeDataForMap(appDataList, DataConstants.ROOT_DATA_ID);
    }

    @Override
    public List<AppData> selectAppDataChildrenByDataId(Long dataId) {
        return this.appDataMapper.selectAppDataChildrenByDataId(dataId);
    }

    @Override
    public List<AppData> selectAppDataList(AppData appData) {
        return this.appDataMapper.selectAppDataList(appData);
    }

    @Override
    public int insertAppData(AppData appData) {
        if (appData.getParentId() == null) {
            appData.setParentId(DataConstants.ROOT_DATA_ID);
        }

        AppData parentData = this.appDataMapper.selectAppDataByDataId(appData.getParentId());
        appData.setAncestors(parentData.getAncestors() + "," + appData.getParentId());
        appData.setCreateBy(SecurityUtils.getUsername());
        return this.appDataMapper.insertAppData(appData);
    }

    @Override
    public int updateAppData(AppDataTreeNode appDataTreeNode) {
        AppData appData = this.convertAppDataTreeNodeToAppData(appDataTreeNode);
        if (appData != null) {
            appData.setUpdateTime(DateUtils.getNowDate());
            return this.appDataMapper.updateAppData(appData);
        } else {
            return 0;
        }
    }

    @Override
    public int deleteAppDataByDataIds(Long[] dataIds) {
        return this.appDataMapper.deleteAppDataByDataIds(dataIds);
    }

    @Override
    public int deleteAppDataByDataId(Long dataId) {
        return this.appDataMapper.deleteAppDataByDataId(dataId);
    }

    @Override
    public boolean existsAppData(long dataId) {
        LambdaQueryWrapper<AppData> queryWrapper = Wrappers.lambdaQuery();
        return this.getBaseMapper().exists(queryWrapper.eq(AppData::getDataId, dataId));
    }

    @Override
    public List<AppData> buildTreeDataForManager(List<AppData> dataList, long dataId) {
        List<AppData> list;
        if (dataList != null && !dataList.isEmpty()) {
            list = dataList;
        } else {
            AppData parentAppData = this.appDataMapper.selectAppDataByDataId(dataId);
            List<AppData> childrenAppData = this.appDataMapper.selectAppDataChildrenByDataId(dataId);
            list = CollUtil.union(childrenAppData, Collections.singletonList(parentAppData)).stream().toList();
        }

        return this.buildDataTree(list);
    }

    @Override
    public List<AppDataTreeNode> buildTreeDataForMap(List<AppData> dataList, long dataId) {
        List<AppDataTreeNode> appDataTreeNodes = new ArrayList<>();
        List<AppData> appDataList = this.buildTreeDataForManager(dataList, dataId);
        this.appDataTreeToAppDataTreeNodeTree(appDataTreeNodes, appDataList);
        return appDataTreeNodes;
    }

    private void appDataTreeToAppDataTreeNodeTree(List<AppDataTreeNode> appDataTreeNodes, List<AppData> appDataList) {
        AppDataTreeNode appDataTreeNode;
        for (Iterator<AppData> var3 = appDataList.iterator(); var3.hasNext(); appDataTreeNodes.add(appDataTreeNode)) {
            AppData appData = var3.next();
            appDataTreeNode = new AppDataTreeNode();
            appDataTreeNode.setDataId(appData.getDataId());
            appDataTreeNode.setLabel(appData.getDataName());
            appDataTreeNode.setDescribe(appData.getDescription());
            String properties = appData.getProperties();
            JSONObject extendedProperties;
            if (StringUtils.hasText(properties)) {
                extendedProperties = new JSONObject(properties);
                appDataTreeNode.setIp(extendedProperties.optString("ip"));
                appDataTreeNode.setPort(extendedProperties.optString("port"));
                appDataTreeNode.setLayerServiceType(extendedProperties.optString("layerServiceType"));
                appDataTreeNode.setName(extendedProperties.optString("name"));
                appDataTreeNode.setSearchName(extendedProperties.optString("searchName"));
                appDataTreeNode.setSearchServiceType(extendedProperties.optString("searchServiceType"));
                appDataTreeNode.setServerUrl(extendedProperties.optString("serverUrl"));
                appDataTreeNode.setTokenKey(extendedProperties.optString("tokenKey"));
                appDataTreeNode.setToken(extendedProperties.optString("token"));
                appDataTreeNode.setWfsUrl(extendedProperties.optString("wfsUrl"));
                appDataTreeNode.setModel(extendedProperties.optString("model"));
                appDataTreeNode.setExtent(extendedProperties.optString("extent"));
                appDataTreeNode.setGuid(extendedProperties.optString("guid"));
                appDataTreeNode.setData(extendedProperties.optString("data"));
                appDataTreeNode.setGdbTypeList(extendedProperties.optString("gdbTypeList"));
            }

            extendedProperties = new JSONObject(appData.getExtendedProperties() == null ? "{}" : appData.getExtendedProperties());
            if (!extendedProperties.has("location")) {
                extendedProperties.put("location", true);
            }

            if (!extendedProperties.has("checked")) {
                extendedProperties.put("checked", false);
            }

            if (!extendedProperties.has("roll")) {
                extendedProperties.put("roll", true);
            }

            if (!extendedProperties.has("selfAdaption")) {
                extendedProperties.put("selfAdaption", false);
            }

            if (!extendedProperties.has("legend")) {
                extendedProperties.put("legend", "");
            }

            appDataTreeNode.setExtend(JsonUtils.parseMap(extendedProperties.toString()));
            if (appData.getDataType() == 1) {
                appDataTreeNode.setLayerProperty(JsonUtils.parseMap(appData.getLayerProperties()));
            } else {
                List<AppDataTreeNode> children = new ArrayList<>();
                this.appDataTreeToAppDataTreeNodeTree(children, appData.getChildren());
                appDataTreeNode.setChildren(children);
            }
        }

    }

    @Override
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public boolean updateTreeData(List<AppData> dataList) {
        Long userId = SecurityUtils.getUserId();
        List<AppData> oldTreeData = this.selectAppDataTreeByUserId(userId);
        List<AppData> newTreeData = this.convertToDataNodeList(dataList);
        List<AppData> nodesToAdd = this.getNodesToAdd(newTreeData, oldTreeData);
        List<AppData> nodesToUpdate = this.getNodesToUpdate(newTreeData, oldTreeData);
        List<Long> nodeIdsToDelete = this.getNodeIdsToDelete(newTreeData, oldTreeData);
        if (!nodeIdsToDelete.isEmpty()) {
            Long[] dataIds = nodeIdsToDelete.toArray(new Long[0]);
            this.appDataMapper.deleteAppDataByDataIds(dataIds);
            this.appRoleDataMapper.deleteRoleDataByDataIds(dataIds);
        }

        if (!nodesToAdd.isEmpty()) {
            this.appDataMapper.batchInsertAppDataList(nodesToAdd);
        }

        if (!nodesToUpdate.isEmpty()) {
            this.appDataMapper.batchUpdateDataList(nodesToUpdate);
        }

        return true;
    }

    @Override
    public List<AppData> parseDatasFromFile(MultipartFile file) {
        if (file == null) {
            return Collections.emptyList();
        } else {
            try {
                JSONObject jsonObject = new JSONObject();
                this.idCounter.getAndSet((long) this.appDataMapper.findMaxDataId());
                List<Long> ancestorList = Collections.singletonList(DataConstants.ROOT_DATA_PARENT_ID);
                String sysConfig = this.sysServiceProxy.selectConfigValueByKey("app.display");
                String data = new String(file.getBytes(), StandardCharsets.UTF_8);
                List<AppData> convert;
                if (data.startsWith("[")) {
                    List<AppDataTreeNode> appDataTreeNodes = JacksonUtils.fromJson((new JSONArray(data)).toString(), new TypeReference<List<AppDataTreeNode>>() {
                    });
                    AppDataTreeNode appDataTreeNode = new AppDataTreeNode();
                    appDataTreeNode.setName("root");
                    appDataTreeNode.setChildren(appDataTreeNodes);
                    convert = this.convert(appDataTreeNode, DataConstants.ROOT_DATA_PARENT_ID, ancestorList, true);
                } else {
                    JSONObject catalogTreeData = new JSONObject(data);
                    JSONObject paramConfigJson = catalogTreeData.optJSONObject("paramConfig");
                    JSONObject newObj = new JSONObject();
                    Iterator<String> it = paramConfigJson.keys();

                    while (it.hasNext()) {
                        String key = it.next();
                        newObj.put(key.toLowerCase(), paramConfigJson.get(key));
                    }

                    AppDataParamConfig paramConfig = JacksonUtils.fromJson(newObj.toString(), AppDataParamConfig.class);
                    JSONObject otherConfig = catalogTreeData.optJSONObject("otherConfig");
                    String nonSpatialTag = null;
                    if (otherConfig != null) {
                        nonSpatialTag = otherConfig.optString("nonSpatial");
                    }

                    JSONObject treeConfig = catalogTreeData.optJSONObject("treeConfig");
                    if (treeConfig == null) {
                        throw new ServiceException("获取数据异常，请检查数据");
                    }

                    String treeDataStr = treeConfig.optString("treeData");
                    jsonObject.put(paramConfig.getName(), "root");
                    jsonObject.put(paramConfig.getChildren(), treeDataStr);
                    convert = this.convert(jsonObject, DataConstants.ROOT_DATA_PARENT_ID, ancestorList, paramConfig, nonSpatialTag, true, sysConfig);
                }

                convert.remove(0);
                this.setOrderNum(convert);
                return convert;
            } catch (IOException var15) {
                throw new ServiceException(var15.getMessage());
            }
        }
    }

    private List<AppData> convert(JSONObject treeNode, Long parentId, List<Long> ancestors, AppDataParamConfig paramConfig, String nonSpatialTag, boolean isRoot, String sysConfig) {
        List<AppData> result = new ArrayList<>();
        Long currentId = isRoot ? DataConstants.ROOT_DATA_ID : this.idCounter.incrementAndGet();
        List<Long> currentAncestors = new ArrayList<>(ancestors);
        currentAncestors.add(currentId);
        AppData appData = new AppData();
        String name = treeNode.optString(paramConfig.getName());
        String title = treeNode.optString(paramConfig.getTitle());
        if (title.equals(nonSpatialTag)) {
            treeNode.put(paramConfig.getLayerServiceType(), "noSpatialData");
        }

        appData.setDataName(name);
        appData.setDescription(title);
        this.setAppDataCommon(appData, currentId, parentId, ancestors);
        boolean hasChildren = treeNode.has(paramConfig.getChildren());
        appData.setDataType(hasChildren ? 0 : 1);
        if (!hasChildren) {
            treeNode.remove(paramConfig.getName());
            treeNode.remove(paramConfig.getTitle());
            appData.setProperties(treeNode.toString());
            this.setAppDataLayerProperties(appData, sysConfig, null);
            appData.setExtendedProperties("{\"location\": true, \"checked\": false, \"roll\": true, \"selfAdaption\": false, \"legend\": \"\"}");
        }

        result.add(appData);
        if (hasChildren) {
            JSONArray children = new JSONArray(treeNode.optString(paramConfig.getChildren()));

            for (int i = 0; i < children.length(); ++i) {
                JSONObject jsonObject = children.optJSONObject(i);
                result.addAll(this.convert(jsonObject, currentId, currentAncestors, paramConfig, nonSpatialTag, false, sysConfig));
            }
        }

        return result;
    }

    private List<AppData> convert(AppDataTreeNode appDataTreeNode, Long parentId, List<Long> ancestors, boolean isRoot) {
        List<AppData> result = new ArrayList<>();
        Long currentId = isRoot ? DataConstants.ROOT_DATA_ID : this.idCounter.incrementAndGet();
        List<Long> currentAncestors = new ArrayList<>(ancestors);
        currentAncestors.add(currentId);
        AppData appData = new AppData();
        appData.setDataName(appDataTreeNode.getLabel());
        appData.setDescription(appDataTreeNode.getDescribe());
        this.setAppDataCommon(appData, currentId, parentId, ancestors);
        List<AppDataTreeNode> children = appDataTreeNode.getChildren();
        if (children != null) {
            appData.setDataType(0);
            result.add(appData);
            for (AppDataTreeNode child : children) {
                result.addAll(this.convert(child, currentId, currentAncestors, false));
            }
        } else {
            appData.setDataType(1);
            JSONObject propertiesJsonObject = new JSONObject();
            propertiesJsonObject.put("ip", appDataTreeNode.getIp());
            propertiesJsonObject.put("port", appDataTreeNode.getPort());
            propertiesJsonObject.put("layerServiceType", appDataTreeNode.getLayerServiceType());
            propertiesJsonObject.put("name", appDataTreeNode.getName());
            propertiesJsonObject.put("searchName", appDataTreeNode.getSearchName());
            propertiesJsonObject.put("searchServiceType", appDataTreeNode.getSearchServiceType());
            propertiesJsonObject.put("serverUrl", appDataTreeNode.getServerUrl());
            propertiesJsonObject.put("tokenKey", appDataTreeNode.getTokenKey());
            propertiesJsonObject.put("token", appDataTreeNode.getToken());
            propertiesJsonObject.put("wfsUrl", appDataTreeNode.getWfsUrl());
            propertiesJsonObject.put("model", appDataTreeNode.getModel());
            propertiesJsonObject.put("extent", appDataTreeNode.getExtent());
            propertiesJsonObject.put("guid", appDataTreeNode.getGuid());
            propertiesJsonObject.put("data", appDataTreeNode.getData());
            propertiesJsonObject.put("gdbTypeList", appDataTreeNode.getGdbTypeList());
            appData.setProperties(propertiesJsonObject.toString());
            appData.setLayerProperties(JacksonUtils.toJson(appDataTreeNode.getLayerProperty()));
            appData.setExtendedProperties(JacksonUtils.toJson(appDataTreeNode.getExtend()));
            result.add(appData);
        }

        return result;
    }

    @Override
    public List<AppData> parseDatasFromCloud(String data) {
        try {
            this.idCounter.getAndSet((long) this.appDataMapper.findMaxDataId());
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("dataName", "root");
            jsonObject.put("children", data);
            if (StringUtils.isNotEmpty(data)) {
                List<Long> ancestorList = Collections.singletonList(DataConstants.ROOT_DATA_PARENT_ID);
                String sysConfig = this.sysServiceProxy.selectConfigValueByKey("app.display");
                List<AppData> convert = this.convert(jsonObject, DataConstants.ROOT_DATA_PARENT_ID, ancestorList, true, sysConfig, false);
                convert.remove(0);
                this.setOrderNum(convert);
                return convert;
            } else {
                return Collections.emptyList();
            }
        } catch (Exception var6) {
            throw new ServiceException(var6.getMessage());
        }
    }

    private List<AppData> convert(JSONObject treeNode, Long parentId, List<Long> ancestors, boolean isRoot, String sysConfig, boolean isAutoBuild) {
        List<AppData> result = new ArrayList<>();
        AppData appData = new AppData();
        boolean isFirstLevel = treeNode.has("treeInfo");
        Long currentId;
        if (!isRoot && !isFirstLevel) {
            currentId = this.idCounter.incrementAndGet();
        } else {
            currentId = DataConstants.ROOT_DATA_ID;
        }

        List<Long> currentAncestors = new ArrayList<>(ancestors);
        this.setAppDataCommon(appData, currentId, parentId, ancestors);
        JSONObject nodeProperties = treeNode.has("customProperties") ? treeNode.optJSONObject("customProperties") : treeNode;
        appData.setDescription(nodeProperties.optString("describe"));
        boolean hasChildren = treeNode.has("children");
        JSONArray children;
        Iterator var16;
        Object obj;
        if (!isFirstLevel && !hasChildren) {
            JSONObject propertiesJsonObject;
            if (isAutoBuild) {
                appData.setDataType(1);
                propertiesJsonObject = new JSONObject();
                String name = nodeProperties.optString("name");
                appData.setDataName(nodeProperties.optString("label", name));
                propertiesJsonObject.put("ip", nodeProperties.optString("ip", this.getDefaultIpOrPort("ip")));
                propertiesJsonObject.put("port", nodeProperties.optString("port", this.getDefaultIpOrPort("port")));
                String cloudServiceType = nodeProperties.optString("layerServiceType");
                DataServiceType catalogServiceType = this.cloudServiceTypeToCatalogServiceType(cloudServiceType);
                propertiesJsonObject.put("layerServiceType", catalogServiceType == DataServiceType.UNKNOWN ? "" : catalogServiceType.toString());
                propertiesJsonObject.put("name", name);
                propertiesJsonObject.putOpt("searchName", nodeProperties.optString("searchName", (String) null));
                propertiesJsonObject.put("searchServiceType", this.getSearchServiceType(nodeProperties, catalogServiceType));
                propertiesJsonObject.put("serverUrl", this.getServerUrl(nodeProperties, cloudServiceType, name));
                propertiesJsonObject.putOpt("tokenKey", nodeProperties.optString("tokenKey", (String) null));
                propertiesJsonObject.putOpt("token", nodeProperties.optString("token", (String) null));
                propertiesJsonObject.putOpt("wfsUrl", nodeProperties.optString("wfsUrl", (String) null));
                propertiesJsonObject.putOpt("model", nodeProperties.optString("model", (String) null));
                propertiesJsonObject.putOpt("extent", nodeProperties.optString("extent", (String) null));
                propertiesJsonObject.putOpt("guid", nodeProperties.optString("guid", (String) null));
                propertiesJsonObject.putOpt("data", nodeProperties.optString("data", (String) null));
                propertiesJsonObject.putOpt("gdbTypeList", nodeProperties.optString("gdbTypeList", (String) null));
                appData.setProperties(propertiesJsonObject.toString());
                this.setAppDataLayerProperties(appData, sysConfig, nodeProperties);
                this.setAppDataExtendedProperties(appData, nodeProperties);
                result.add(appData);
            } else {
                propertiesJsonObject = treeNode.optJSONObject("resource");
                children = propertiesJsonObject.optJSONArray("services");
                if (!children.isEmpty()) {
                    var16 = children.iterator();

                    while (var16.hasNext()) {
                        obj = var16.next();
                        AppData service = this.createServiceData((JSONObject) obj, parentId, ancestors, sysConfig);
                        result.add(service);
                    }
                }
            }
        } else {
            appData.setDataName(nodeProperties.optString("label", nodeProperties.optString("name")));
            appData.setDataType(0);
            if (!isFirstLevel) {
                currentAncestors.add(currentId);
                result.add(appData);
            }

            String childrenString;
            if (isFirstLevel) {
                if (treeNode.optInt("type") == 1) {
                    childrenString = treeNode.optString("treeInfo");
                    isAutoBuild = false;
                } else {
                    childrenString = treeNode.optString("customTreeInfo");
                    isAutoBuild = true;
                }
            } else {
                childrenString = treeNode.optString("children");
            }

            if (StringUtils.isNotEmpty(childrenString)) {
                children = new JSONArray(childrenString);
                var16 = children.iterator();

                while (var16.hasNext()) {
                    obj = var16.next();
                    JSONObject jsonObject = (JSONObject) obj;
                    result.addAll(this.convert(jsonObject, currentId, currentAncestors, false, sysConfig, isAutoBuild));
                }
            }
        }

        return result;
    }

    @Override
    @Transactional(
            rollbackFor = {Exception.class}
    )
    public String importDatas(List<AppData> appDataList, boolean isUpdateSupport, String operName) {
        if (!StringUtils.isNull(appDataList) && !appDataList.isEmpty()) {
            int successNum = 0;
            int failureNum = 0;
            StringBuilder successMsg = new StringBuilder();
            StringBuilder failureMsg = new StringBuilder();

            for (AppData appData : appDataList) {
                try {
                    AppData exist = this.checkDataExist(appData);
                    if (exist == null) {
                        BeanValidators.validateWithException(this.validator, appData, new Class[0]);
                        appData.setCreateBy(operName);
                        this.appDataMapper.insertAppData(appData);
                        ++successNum;
                        successMsg.append("<br/>").append(successNum).append("、数据 ").append(appData.getDataName()).append(" 导入成功");
                    } else if (isUpdateSupport) {
                        BeanValidators.validateWithException(this.validator, appData, new Class[0]);
                        appData.setUpdateBy(operName);
                        Long oldDataId = appData.getDataId();
                        Long newDataId = exist.getDataId();
                        appData.setDataId(newDataId);
                        if (this.isNeedUpdate(appData, exist)) {
                            this.appDataMapper.updateAppData(appData);
                            appDataList.stream().filter((data) -> data.getAncestors().contains(oldDataId.toString())).forEach((data) -> {
                                if (data.getParentId().equals(oldDataId)) {
                                    data.setParentId(newDataId);
                                }

                                String ancestors = data.getAncestors();
                                String newAncestors = ancestors.replace(oldDataId.toString(), newDataId.toString());
                                data.setAncestors(newAncestors);
                            });
                            ++successNum;
                            successMsg.append("<br/>").append(successNum).append("、数据 ").append(appData.getDataName()).append(" 更新成功");
                        }
                    } else {
                        ++failureNum;
                        failureMsg.append("<br/>").append(failureNum).append("、数据 ").append(appData.getDataName()).append(" 已存在");
                    }
                } catch (Exception var13) {
                    ++failureNum;
                    String msg = "<br/>" + failureNum + "、数据 " + appData.getDataName() + " 导入失败：";
                    failureMsg.append(msg).append(var13.getMessage());
                    this.log.error(msg, var13);
                }
            }

            if (failureNum > 0) {
                failureMsg.insert(0, "很抱歉，导入失败！共 " + failureNum + " 条数据格式不正确，错误如下：");
                throw new ServiceException(failureMsg.toString());
            } else {
                successMsg.insert(0, "恭喜您，数据已全部导入成功！共 " + successNum + " 条，数据如下：");
                return successMsg.toString();
            }
        } else {
            throw new ServiceException("导入数据目录数据不能为空！");
        }
    }

    @Override
    public void exportData(Long userId, HttpServletResponse response) {
        try {
            List<AppDataTreeNode> appDataTreeNodes = this.selectAppDataTreeForMapByUserId(userId);
            if (!appDataTreeNodes.isEmpty()) {
                response.reset();
                response.addHeader("Content-Disposition", "attachment;filename=export.json");
                response.setContentType("application/octet-stream");
                OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
                toClient.write(Objects.requireNonNull(JacksonUtils.toJson(appDataTreeNodes.get(0).getChildren())).getBytes());
                toClient.flush();
                toClient.close();
            }
        } catch (Exception var5) {
            this.log.error("导出数据目录出现异常", var5);
        }

    }

    private List<AppData> buildDataTree(List<AppData> dataList) {
        List<AppData> returnList = new ArrayList<>();
        List<Long> tempList = dataList.stream().map(AppData::getDataId).toList();

        for (AppData appData : dataList) {
            if (!tempList.contains(appData.getParentId())) {
                this.recursionFn(dataList, appData);
                returnList.add(appData);
            }
        }

        if (returnList.isEmpty()) {
            returnList = dataList;
        }

        return returnList;
    }

    private void recursionFn(List<AppData> list, AppData t) {
        List<AppData> childList = this.getChildList(list, t);
        t.setChildren(childList);

        for (AppData tChild : childList) {
            if (this.hasChild(list, tChild)) {
                this.recursionFn(list, tChild);
            }
        }

    }

    private List<AppData> getChildList(List<AppData> list, AppData t) {
        List<AppData> dataList = new ArrayList<>();
        for (AppData n : list) {
            if (StringUtils.isNotNull(n.getParentId()) && Objects.equals(n.getParentId(), t.getDataId())) {
                dataList.add(n);
            }
        }
        return dataList.stream().sorted(Comparator.comparingInt(AppData::getOrderNum)).collect(Collectors.toList());
    }

    private boolean hasChild(List<AppData> list, AppData t) {
        return !this.getChildList(list, t).isEmpty();
    }

    private List<AppData> getNodesToAdd(List<AppData> newTreeData, List<AppData> oldTreeData) {
        List<AppData> nodesToAdd = new ArrayList<>();
        String username = SecurityUtils.getUsername();

        for (AppData newNode : newTreeData) {
            if (this.findNode(newNode, oldTreeData) == null) {
                newNode.setCreateBy(username);
                nodesToAdd.add(newNode);
            }
        }

        return nodesToAdd;
    }

    private List<AppData> getNodesToUpdate(List<AppData> newTreeData, List<AppData> oldTreeData) {
        List<AppData> nodesToUpdate = new ArrayList<>();
        String username = SecurityUtils.getUsername();

        for (AppData newNode : newTreeData) {
            AppData oldNode = this.findNode(newNode, oldTreeData);
            if (oldNode != null && !newNode.equals(oldNode)) {
                newNode.setUpdateBy(username);
                nodesToUpdate.add(newNode);
            }
        }

        return nodesToUpdate;
    }

    private List<Long> getNodeIdsToDelete(List<AppData> newTreeData, List<AppData> oldTreeData) {
        List<Long> nodeIdsToDelete = new ArrayList<>();

        for (AppData oldNode : oldTreeData) {
            if (this.findNode(oldNode, newTreeData) == null) {
                nodeIdsToDelete.add(oldNode.getDataId());
            }
        }

        return nodeIdsToDelete;
    }

    private AppData findNode(AppData node, List<AppData> nodeList) {
        return nodeList.stream().filter((item) -> item.getDataId().equals(node.getDataId())).findAny().orElse(null);
    }

    private List<AppData> convertToDataNodeList(List<AppData> treeNodeList) {
        List<AppData> dataNodeList = new ArrayList<>();

        for (AppData treeNodeDto : treeNodeList) {
            AppData appData = new AppData();
            BeanUtils.copyBeanProp(appData, treeNodeDto);
            appData.setChildren(Collections.emptyList());
            dataNodeList.add(appData);
            if (treeNodeDto.getChildren() != null && !treeNodeDto.getChildren().isEmpty()) {
                dataNodeList.addAll(this.convertToDataNodeList(treeNodeDto.getChildren()));
            }
        }

        return dataNodeList;
    }

    private DataServiceType cloudServiceTypeToCatalogServiceType(String cloudServiceType) {
        if (StrUtil.isNotBlank(cloudServiceType)) {
            CloudProtocolType cloudProtocolType = CloudProtocolType.valueOf(cloudServiceType);

            return switch (cloudProtocolType) {
                case IGSRestMap -> DataServiceType.IGSIMAGE;
                case IGSRestTile -> DataServiceType.IGSTILED;
                case IGSRestVectorTile -> DataServiceType.VTTILES;
                case IGSRestM3D -> DataServiceType.ModelCache;
                case IGSRest3DTiles -> DataServiceType.TILE3D;
                case IGSRestScene -> DataServiceType.IGSScene;
                case IGSRestTerrain -> DataServiceType.STKTerrain;
                case WMS -> DataServiceType.WMS;
                case WMTS -> DataServiceType.WMTS;
                case ArcGISRestMapServer -> DataServiceType.IMAGEARCGIS;
                default -> DataServiceType.UNKNOWN;
            };
        } else {
            return DataServiceType.UNKNOWN;
        }
    }

    private AppData createServiceData(JSONObject serviceJsonObject, Long parentId, List<Long> ancestors, String sysConfig) {
        AppData service = new AppData();
        service.setDataName(serviceJsonObject.optString("name"));
        service.setDataType(1);
        this.setAppDataCommon(service, this.idCounter.incrementAndGet(), parentId, ancestors);
        JSONArray protocolTypesJsonArray = serviceJsonObject.getJSONArray("protocolTypes");
        String serviceType = protocolTypesJsonArray.optString(0);
        DataServiceType catalogServiceType = this.cloudServiceTypeToCatalogServiceType(serviceType);
        JSONObject propertyJsonObject = new JSONObject();
        propertyJsonObject.put("layerServiceType", catalogServiceType == DataServiceType.UNKNOWN ? "" : catalogServiceType.toString());
        propertyJsonObject.put("ip", serviceJsonObject.optString("ip"));
        propertyJsonObject.put("port", serviceJsonObject.optString("port"));
        String restServerPrefix = null;
        if (DataServiceType.ModelCache.equals(catalogServiceType)) {
            restServerPrefix = CloudProtocolType.IGSRestM3D.getRestServerPrefix();
        } else if (DataServiceType.IGSScene.equals(catalogServiceType)) {
            restServerPrefix = CloudProtocolType.IGSRestScene.getRestServerPrefix();
        } else if (DataServiceType.TILE3D.equals(catalogServiceType)) {
            restServerPrefix = CloudProtocolType.IGSRest3DTiles.getRestServerPrefix();
        }

        if (StringUtils.isNotEmpty(restServerPrefix)) {
            String proto = serviceJsonObject.optString("proto");
            String ip = serviceJsonObject.optString("ip");
            String port = serviceJsonObject.optString("port");
            String folder = serviceJsonObject.optString("folder");
            String name = serviceJsonObject.optString("name");
            String serverUrl = String.format("%s://%s:%s/igs/rest/services/%s/%s/%s", proto, ip, port, StringUtils.isEmpty(folder) ? "" : folder + "/", name, restServerPrefix);
            propertyJsonObject.put("serverUrl", serverUrl);
        }

        service.setProperties(propertyJsonObject.toString());
        this.setAppDataLayerProperties(service, sysConfig, (JSONObject) null);
        service.setExtendedProperties("{\"location\": true, \"checked\": false, \"roll\": true, \"selfAdaption\": false, \"legend\": \"\"}");
        return service;
    }

    private void setOrderNum(List<AppData> appDataList) {
        Map<Long, List<AppData>> groupByParentId = appDataList.stream().collect(Collectors.groupingBy(AppData::getParentId));
        groupByParentId.forEach((parentId, list) -> {
            int childrenDataMinOrderNum = parentId.equals(DataConstants.ROOT_DATA_ID) ? Optional.ofNullable(this.appDataMapper.findChildrenDataMinOrderNum(parentId)).orElse(0) : 0;
            int[] orderNum = new int[]{0};
            list.forEach((appData) -> {
                appData.setOrderNum(orderNum[0]);
                if (childrenDataMinOrderNum != 0) {
                    orderNum[0] += childrenDataMinOrderNum / list.size();
                } else {
                    orderNum[0] += DataConstants.DEFAULT_ORDER_NUM;
                }

            });
        });
    }

    private void setAppDataCommon(AppData appData, Long currentId, Long parentId, List<Long> ancestors) {
        appData.setDataId(currentId);
        appData.setParentId(parentId);
        appData.setAncestors(ancestors.stream().map(Object::toString).collect(Collectors.joining(",")));
    }

    private void setAppDataLayerProperties(AppData appData, String sysConfig, JSONObject treeNode) {
        JSONObject properties = new JSONObject("{\"alpha\": 100, \"maximumScreenSpaceError\": 16, \"luminanceAtZenith\": 0.2, \"scaleX\": 1, \"scaleY\": 1, \"scaleZ\": 1, \"enablePopup\": false, \"enableModelSwitch\": false, \"enableModelStretch\": false, \"offset\": -2, \"fillClip\": false}");
        JSONObject config = new JSONObject(sysConfig);
        int maximumScreenSpaceError;
        double luminanceAtZenith;
        if (treeNode != null) {
            maximumScreenSpaceError = treeNode.optInt("maximumScreenSpaceError", config.optInt("maximumScreenSpaceError", 16));
            properties.put("maximumScreenSpaceError", maximumScreenSpaceError);
            luminanceAtZenith = treeNode.optDouble("luminanceAtZenith", config.optDouble("luminanceAtZenith", 0.2));
            properties.put("luminanceAtZenith", luminanceAtZenith);
            int alpha = treeNode.optInt("alpha", 100);
            properties.put("alpha", alpha);
            int scaleX = treeNode.optInt("scaleX", 1);
            properties.put("scaleX", scaleX);
            int scaleY = treeNode.optInt("scaleY", 1);
            properties.put("scaleY", scaleY);
            int scaleZ = treeNode.optInt("scaleZ", 1);
            properties.put("scaleZ", scaleZ);
            boolean enablePop = treeNode.optBoolean("enablePopup", false);
            properties.put("enablePopup", enablePop);
            boolean enableModelSwitch = treeNode.optBoolean("enableModelSwitch", false);
            properties.put("enableModelSwitch", enableModelSwitch);
            boolean enableModelStretch = treeNode.optBoolean("enableModelStretch", false);
            properties.put("enableModelStretch", enableModelStretch);
            int offset = treeNode.optInt("offset", -2);
            properties.put("offset", offset);
            boolean fillClip = treeNode.optBoolean("fillClip", false);
            properties.put("fillClip", fillClip);
            boolean hasSectionGeometry = treeNode.optBoolean("hasSectionGeometry", false);
            properties.put("hasSectionGeometry", hasSectionGeometry);
        } else if (StringUtils.isNotEmpty(sysConfig)) {
            maximumScreenSpaceError = config.optInt("maximumScreenSpaceError", 16);
            properties.put("maximumScreenSpaceError", maximumScreenSpaceError);
            luminanceAtZenith = config.optDouble("luminanceAtZenith", 0.2);
            properties.put("luminanceAtZenith", luminanceAtZenith);
        }

        appData.setLayerProperties(properties.toString());
    }

    private void setAppDataExtendedProperties(AppData appData, JSONObject treeNode) {
        List<String> needsRemoveList = Arrays.asList("label", "describe", "ip", "port", "layerServiceType", "name", "searchName", "searchServiceType", "serverUrl", "tokenKey", "token", "wfsUrl", "model", "extent", "guid", "data", "gdbTypeList", "alpha", "maximumScreenSpaceError", "luminanceAtZenith", "scaleX", "scaleY", "scaleZ", "enableModelStretch", "enablePopup", "enableModelSwitch", "offset");

        for (String key : needsRemoveList) {
            if (treeNode.has(key)) {
                treeNode.remove(key);
            }
        }

        if (!treeNode.isEmpty()) {
            JSONObject extendedPropertiesJsonObject = new JSONObject();

            for (String key : treeNode.keySet()) {
                extendedPropertiesJsonObject.put(key, treeNode.get(key));
            }

            appData.setExtendedProperties(extendedPropertiesJsonObject.toString());
        } else {
            appData.setExtendedProperties("{}");
        }

    }

    private AppData convertAppDataTreeNodeToAppData(AppDataTreeNode appDataTreeNode) {
        AppData oldAppData = this.selectAppDataByDataId(appDataTreeNode.getDataId());
        if (oldAppData != null) {
            JSONObject oldPropertiesJsonObject = new JSONObject(oldAppData.getProperties());
            JSONObject propertiesJsonObject = new JSONObject();
            propertiesJsonObject.put("ip", this.getPropertyValue(appDataTreeNode.getIp(), oldPropertiesJsonObject, "ip"));
            propertiesJsonObject.put("port", this.getPropertyValue(appDataTreeNode.getPort(), oldPropertiesJsonObject, "port"));
            propertiesJsonObject.put("layerServiceType", this.getPropertyValue(appDataTreeNode.getLayerServiceType(), oldPropertiesJsonObject, "layerServiceType"));
            propertiesJsonObject.put("name", this.getPropertyValue(appDataTreeNode.getName(), oldPropertiesJsonObject, "name"));
            propertiesJsonObject.put("searchName", this.getPropertyValue(appDataTreeNode.getSearchName(), oldPropertiesJsonObject, "searchName"));
            propertiesJsonObject.put("searchServiceType", this.getPropertyValue(appDataTreeNode.getSearchServiceType(), oldPropertiesJsonObject, "searchServiceType"));
            propertiesJsonObject.put("serverUrl", this.getPropertyValue(appDataTreeNode.getServerUrl(), oldPropertiesJsonObject, "serverUrl"));
            propertiesJsonObject.put("tokenKey", this.getPropertyValue(appDataTreeNode.getTokenKey(), oldPropertiesJsonObject, "tokenKey"));
            propertiesJsonObject.put("token", this.getPropertyValue(appDataTreeNode.getToken(), oldPropertiesJsonObject, "token"));
            propertiesJsonObject.put("wfsUrl", this.getPropertyValue(appDataTreeNode.getWfsUrl(), oldPropertiesJsonObject, "wfsUrl"));
            propertiesJsonObject.put("model", this.getPropertyValue(appDataTreeNode.getModel(), oldPropertiesJsonObject, "model"));
            propertiesJsonObject.put("extent", this.getPropertyValue(appDataTreeNode.getExtent(), oldPropertiesJsonObject, "extent"));
            propertiesJsonObject.put("guid", this.getPropertyValue(appDataTreeNode.getGuid(), oldPropertiesJsonObject, "guid"));
            propertiesJsonObject.put("data", this.getPropertyValue(appDataTreeNode.getData(), oldPropertiesJsonObject, "data"));
            propertiesJsonObject.put("gdbTypeList", this.getPropertyValue(appDataTreeNode.getGdbTypeList(), oldPropertiesJsonObject, "gdbTypeList"));
            AppData appData = new AppData();
            appData.setDataId(appDataTreeNode.getDataId());
            appData.setDataName(appDataTreeNode.getLabel());
            appData.setDescription(appDataTreeNode.getDescribe());
            appData.setProperties(propertiesJsonObject.toString());
            appData.setLayerProperties(JacksonUtils.toJson(appDataTreeNode.getLayerProperty()));
            appData.setExtendedProperties(JacksonUtils.toJson(appDataTreeNode.getExtend()));
            return appData;
        } else {
            return null;
        }
    }

    private String getPropertyValue(String value, JSONObject oldPropertiesJsonObject, String key) {
        return Optional.ofNullable(value).orElse(oldPropertiesJsonObject.optString(key));
    }

    private boolean isNeedUpdate(AppData newData, AppData oldData) {
        if (newData.getDataType() == 1) {
            JSONObject oldProperties = new JSONObject(oldData.getProperties());
            String layerServiceType = oldProperties.optString("layerServiceType");
            if (StringUtils.isNotEmpty(layerServiceType)) {
                DataServiceType dataServiceType = DataServiceType.fromString(layerServiceType);
                if (dataServiceType == DataServiceType.UNKNOWN) {
                    return false;
                }
            }

            JSONObject newProperties = new JSONObject(newData.getProperties());
            List<String> propertiesToCheck = Arrays.asList("searchName", "tokenKey", "token", "wfsUrl", "model", "extent", "guid", "data", "gdbTypeList");

            for (String property : propertiesToCheck) {
                if (!newProperties.has(property) && oldProperties.has(property)) {
                    newProperties.put(property, oldProperties.optString(property));
                }
            }

            newData.setProperties(newProperties.toString());
            JSONObject oldExtendedProperties = new JSONObject(oldData.getExtendedProperties());
            JSONObject newExtendedProperties = new JSONObject(newData.getExtendedProperties());

            for (String key : oldExtendedProperties.keySet()) {
                if (!newExtendedProperties.has(key)) {
                    newExtendedProperties.put(key, oldExtendedProperties.get(key));
                }
            }

            newData.setExtendedProperties(newExtendedProperties.toString());
        }

        return true;
    }

    private AppData checkDataExist(AppData appData) {
        boolean isFolder = appData.getDataType() == 0;
        Long parentId = appData.getParentId();
        if (isFolder) {
            return this.appDataMapper.checkFolderDataUnique(appData.getDataName(), parentId);
        } else {
            JSONObject jsonObject = new JSONObject(appData.getProperties());
            String serverUrl = jsonObject.optString("serverUrl");
            return this.appDataMapper.checkNodeDataUnique(serverUrl, parentId);
        }
    }

    private String getServerUrl(JSONObject treeNode, String cloudServiceType, String name) {
        if (treeNode.has("serverUrl")) {
            Optional<Object> optionalServerUrl = Optional.ofNullable(treeNode.get("serverUrl"));
            if (optionalServerUrl.isPresent()) {
                Object serverUrl = optionalServerUrl.get();
                return "null".equalsIgnoreCase(String.valueOf(serverUrl)) ? this.getDefaultServerUrl(cloudServiceType, name) : DataConstants.REGISTER_SERVICE_REPLACE_SERVER_ADDR_REG.matcher(String.valueOf(serverUrl)).replaceAll(this.getCloudRegistrationAddress());
            } else {
                return "";
            }
        } else {
            return this.getDefaultServerUrl(cloudServiceType, name);
        }
    }

    private String getDefaultServerUrl(String cloudServiceType, String name) {
        String serverUrl = "";
        if (StrUtil.isNotBlank(cloudServiceType)) {
            CloudProtocolType cloudProtocolType = CloudProtocolType.valueOf(cloudServiceType);
            List<CloudProtocolType> newVersionTypes = Arrays.asList(CloudProtocolType.IGSRest3DTiles, CloudProtocolType.IGSRestM3D, CloudProtocolType.IGSRestTerrain, CloudProtocolType.IGSRestScene, CloudProtocolType.WMS, CloudProtocolType.WMTS, CloudProtocolType.ArcGISRestMapServer);
            if (newVersionTypes.contains(cloudProtocolType) && name.contains(":")) {
                name = name.replace(":", "/");
            }

            String urlSuffix;
            switch (cloudProtocolType) {
                case IGSRestMap:
                    urlSuffix = "/igs/rest/mrms/docs/" + name;
                    break;
                case IGSRestTile:
                    urlSuffix = "/igs/rest/mrms/tile/" + name;
                    break;
                case IGSRestVectorTile:
                    urlSuffix = "/igs/rest/mrcs/vtiles/0/" + name;
                    break;
                case IGSRestM3D:
                case IGSRest3DTiles:
                case IGSRestScene:
                case IGSRestTerrain:
                case WMS:
                case WMTS:
                case ArcGISRestMapServer:
                    urlSuffix = "/igs/rest/services/" + name + "/" + cloudProtocolType.getRestServerPrefix();
                    if (cloudProtocolType == CloudProtocolType.IGSRest3DTiles) {
                        urlSuffix = urlSuffix + "/tileset.json";
                    }
                    break;
                default:
                    urlSuffix = "";
            }

            if (StringUtils.isNotEmpty(urlSuffix)) {
                String url = this.getCloudRegistrationAddress();
                if (StringUtils.isNotEmpty(url)) {
                    serverUrl = url + urlSuffix;
                }
            }
        }

        return serverUrl;
    }

    private String getDefaultIpOrPort(String type) {
        String url = this.getCloudRegistrationAddress();
        if (StringUtils.isNotEmpty(url)) {
            Pattern pattern = Pattern.compile("^(?:https?://)?([\\d.]+):(\\d+)");
            Matcher matcher = pattern.matcher(url);
            if (matcher.matches()) {
                if ("ip".equals(type)) {
                    return matcher.group(1);
                }

                return matcher.group(2);
            }
        }

        return null;
    }

    private String getSearchServiceType(JSONObject treeNode, DataServiceType catalogServiceType) {
        if (treeNode.has("searchServiceType")) {
            String searchServiceTypeStr = treeNode.optString("searchServiceType");
            if (searchServiceTypeStr.startsWith("[")) {
                JSONArray searchServiceTypeArray = new JSONArray(searchServiceTypeStr);
                return !searchServiceTypeArray.isEmpty() ? searchServiceTypeArray.optString(0) : "";
            } else {
                return "";
            }
        } else {
            return this.getDefaultSearchServiceType(catalogServiceType);
        }
    }

    private String getDefaultSearchServiceType(DataServiceType catalogServiceType) {
        DataServiceType searchServiceType;
        switch (catalogServiceType) {
            case IGSIMAGE:
            case IGSTILED:
                searchServiceType = DataServiceType.IGSIMAGE;
                break;
            case TILE3D:
            case ModelCache:
            case IGSScene:
            case STKTerrain:
                searchServiceType = DataServiceType.IGSScene;
                break;
            default:
                return "";
        }

        return searchServiceType.toString();
    }

    private String getCloudRegistrationAddress() {
        String sysConfig = this.sysServiceProxy.selectConfigValueByKey("app.resource.catalog");
        JSONObject cloudConfig = new JSONObject(sysConfig);
        return cloudConfig.optString("url");
    }

}
